<?php


namespace EchoPHP\Route;

use EchoPHP\Foundation\Container;
use EchoPHP\Http\Request;
use EchoPHP\Http\Response;
use EchoPHP\Pipeline\Pipeline;
use EchoPHP\Support\Contacts\Responsable;
use Exception;
use BadMethodCallException;
use InvalidArgumentException;
use ReflectionMethod;

class Router {
    protected $container;

    protected $currentRequest;

    protected $rules = [];

    public function __construct(Container $container = null) {
        $this->container = $container;
    }

    /**
     * 获取注册路由列表
     * @param string $rule 路由规则
     * @param string $method 请求方式
     * @return array|mixed|null
     */
    public function routeList($rule = '', $method = '*') {
        if (empty($rule)) {
            return $this->rules;
        }
        return isset($this->rules[$method][$rule]) ? $this->rules[$method][$rule] : null;
    }

    /**
     * 注册get方式路由
     * @param $rule
     * @param $route
     * @param array $option
     * @param array $pattern
     */
    public function get($rule, $route, $option = [], $pattern = []) {
        $this->addRoute($rule, $route, 'GET', $option, $pattern);
    }

    /**
     * 注册post方式路由
     * @param $rule
     * @param $route
     * @param array $option
     * @param array $pattern
     */
    public function post($rule, $route, $option = [], $pattern = []) {
        $this->addRoute($rule, $route, 'POST', $option, $pattern);
    }

    /**
     * 注册get或者post方式路由   暂不考虑restful接口，只支持get或者post方式的请求
     * @param $rule
     * @param $route
     * @param array $option
     * @param array $pattern
     */
    public function any($rule, $route, $option = [], $pattern = []) {
        $this->addRoute($rule, $route, '*', $option, $pattern);
    }

    /**
     * 添加路由规则
     * @param string $rule 路由标识
     * @param string $route 路由地址  Controller\action
     * @param string GET|POST|* $method    请求方式
     * @param array $option 变量参数
     * @param array $pattern 变量规则,正则表达式
     */
    protected function addRoute($rule, $route, $method = '*', array $option = [], array $pattern = []) {
        $method = strtolower($method);
        if ('/' === $rule || '' === $rule) {
            $rule .= '$';
        }
        if ('$' == substr($rule, -1, 1)) {
            $rule = substr($rule, 0, -1);
            $option['complete_match'] = true;
        }
        $rule = '/' != $rule ? ltrim($rule, '/') : '';

        if (false !== strpos($rule, ':')) {
            //进行变量替换，  /user/:name/[:age]  替换为  /user/{name}/{age?}
            $rule = preg_replace(['/\[\:(\w+)\]/', '/\:(\w+)/'], ['<\1?>', '<\1>'], $rule);
        }

        $this->rules[$method][$rule] = [
            'route' => $route,
            'option' => $option,
            'pattern' => $pattern
        ];
    }

    /**
     * 路由执行方法
     * @param Request $request
     * @return mixed
     */
    public function runRoute(Request $request) {
        $middleWare = $this->getMiddleware($request);
        //通过pipeline管道机制，往http请求中添加中间件
        $route = $this;
        return (new Pipeline($this->container))->send($request)->through($middleWare)
            ->then(function ($request) use ($route) {
                return $this->prepareResponse($request, $route->runController($request));
            });
    }

    /**
     * 获取请求操作的中间件，暂不处理，返回空
     * @param Request $request
     * @return array
     */
    public function getMiddleware(Request $request) {
        return [
//            Pipe1::class,
//            Pipe2::class
        ];
    }

    /**
     * 执行控制器方法,如果未能找到控制器及对应方法，则直接抛出404异常
     * @param Request $request
     * @return mixed
     * @throws \Exception
     */
    protected function runController(Request $request) {
        $attributes = $this->parseController($request);
        $request->attribute()->replace($attributes);

        $controller = $attributes['controller'];
        $action = $attributes['action'];
        unset($attributes['controller'], $attributes['action']);

        $controllerInstance = $this->container->make($controller);
        $attributes = $this->resolveClassMethodDependencies($attributes, $controllerInstance, $action);


        if (method_exists($controllerInstance, 'callAction')) {
            return $controllerInstance->callAction($action, $attributes);
        }
        return $controllerInstance->{$action}(...array_values($attributes));
    }

    /**
     * 解析方法的依赖
     * @param array $parameters
     * @param $instance
     * @param $method
     * @return array
     * @throws \ReflectionException
     */
    protected function resolveClassMethodDependencies(array $parameters, $instance, $method) {
        if (!method_exists($instance, $method)) {
            return $parameters;
        }

        $reflector = new ReflectionMethod($instance, $method);
        $args = [];
        foreach ($reflector->getParameters() as $key => $parameter) {
            $paramName = $parameter->getName();
            $paramClass = $parameter->getClass();
            if ($paramClass) {
                $objectParamName = $paramClass->getName();
                if (isset($parameters[$paramName]) && $parameters[$paramName] instanceof $objectParamName) {
                    $args[] = $parameters[$paramName];
                } else {
                    //外部未传入参数，而此参数指定了类对象限制，则通过反射自动实例化这个类实例
                    $args[] = $this->container->make($objectParamName);
                }
            } elseif (isset($parameters[$paramName])) {
                $args[] = $parameters[$paramName];
            } elseif ($parameter->isDefaultValueAvailable()) {
                $args[] = $parameter->getDefaultValue();
            } else {
                throw new InvalidArgumentException('控制器方法缺少参数:' . $paramName);
            }
        }

        return $args;
    }

    /**
     * 从url中解析控制器及操作方法， 优先不使用路由直接判断是否符合默认控制器及操作，如果不能匹配则匹配路由规则
     * @param Request $request
     * @return array    类名,方法名,url中得参数数组
     * @throws \Exception  如果未能解析出正确的控制器，抛出404异常
     */
    protected function parseController(Request $request) {
        $pathInfo = $request->getPathInfo();
        $pathInfo = trim($pathInfo, '/');
        if (empty($pathInfo)) {
            //未设置pathinfo，则直接获取默认控制器及操作名
            [$controller, $action] = $this->getDefaultController();
            if (!($chkResult = $this->checkController($controller, $action))) {
                throw new BadMethodCallException('默认控制器或默认方法不存在');
            }
            $attributes = [];
            $attributes['controller'] = $chkResult[0];
            $attributes['action'] = $chkResult[1];
            return $attributes;
        }
        $pathInfo = $this->trimStaticSuffix($pathInfo);
        //解析pathinfo, 解析出 controller  action 以及其他request attribute 部分信息
        if (false !== ($attributes = $this->parsePathInfo($pathInfo))) {
            return $attributes;
        }
        if (false !== ($attributes = $this->parseWithRoute($pathInfo, $request))) {
            return $attributes;
        }
        throw new BadMethodCallException('控制器或者方法不存在');
    }

    /**
     * 获取默认控制器
     * @return array
     * @throws Exception
     */
    protected function getDefaultController() {
        return [config('app.default_controller', 'Index'), config('app.default_action', 'index')];
    }

    /**
     * 处理pathinfo伪静态后缀
     * todo 从配置文件获取
     * @param string $pathInfo
     * @return string
     */
    protected function trimStaticSuffix($pathInfo) {
        $staticSuffix = 'html';
        if (empty($staticSuffix)) {
            return $pathInfo;
        }
        $staticSuffix = '.' . $staticSuffix;
        if ($staticSuffix === substr($pathInfo, 0 - strlen($staticSuffix))) {
            return substr($pathInfo, 0, 0 - strlen($staticSuffix));
        }
        return $pathInfo;
    }

    /**
     * 解析pathinfo，获取控制器、方法及其他请求参数
     *  只有键值对的参数才会被保存，如  /name/abc 会被解析为 name = abc
     *  /debug 这样会被舍弃， 必须定义为 /debug/1
     * @param $pathInfo
     * @return array|bool
     * @throws
     */
    protected function parsePathInfo($pathInfo) {
        $attributes = [];
        $defaultController = $this->getDefaultController();
        $segments = explode('/', $pathInfo);
        $controller = array_shift($segments);
        if (empty($segments)) {
            $action = $defaultController[1];
        } else {
            $action = array_shift($segments);
        }
        if (!($chkResult = $this->checkController($controller, $action))) {
            return false;
        }
        $attributes['controller'] = $chkResult[0];
        $attributes['action'] = $chkResult[1];
        for ($i = 0; $i < count($segments); $i += 2) {
            if (isset($segments[$i + 1])) {
                $attributes[$segments[$i]] = $segments[$i + 1];
            }
        }
        return $attributes;
    }

    /**
     * 根据路由规则解析地址
     * //实时加载当前模块下定义的路由，自定义路由规则  路由名标识  => 实际控制器位置
     * //路由名标识组成: 标识名称[\{:paramName}] 即标识名称+参数名称+伪静态后缀
     * // case/hrcase/{:caseid}  case/hrcase/2006.html  前缀 case/hrcase(实际使用过程中一级即可，不要太多层级复杂)  caseid=2006  伪静态后缀 html
     * @param $pathInfo
     * @param Request $request
     * @return bool|mixed
     * @throws Exception
     */
    protected function parseWithRoute($pathInfo, Request $request) {
        $routePath = $this->container->make('path.routes');
        $routeFile = $routePath . '/' . MODULE_NAME . '.php';
        if (!is_file($routeFile)) {
            return false;
        }
        //加载路由定义文件
        require $routeFile;

        $routeList = $this->routeList('');

        $methodRoutes = isset($routeList[strtolower($request->getMethod())]) ? $routeList[strtolower($request->getMethod())] : [];
        $matchResult = $this->matchRoute($pathInfo, $methodRoutes);
        if (false === $matchResult) {
            $anyRouteNames = isset($routeList['*']) ? array_keys($routeList['*']) : [];
            $matchResult = $this->matchRoute($pathInfo, $anyRouteNames);
            if (false === $matchResult) {
                return false;
            }
        }

        //解析url地址中剩余参数
        $paramsCount = substr_count($matchResult['rule'], '/');
        $extraParams = array_slice(explode('/', $pathInfo), $paramsCount + 1);
        for ($i = 0; $i < count($extraParams); $i += 2) {
            if (isset($extraParams[$i]) && isset($extraParams[$i + 1]) && !isset($matchResult['vars'][$extraParams[$i]])) {
                $matchResult['vars'][$extraParams[$i]] = $extraParams[$i + 1];
            }
        }

        $route = explode('/', $matchResult['route'], 2);
        if (!($chkResult = $this->checkController($route[0], $route[1]))) {
            return false;
        }
        $attributes = $matchResult['vars'];
        $attributes['controller'] = $chkResult[0];
        $attributes['action'] = $chkResult[1];
        return $attributes;
    }

    /**
     * 校验控制器及对应方法是否存在
     * @param $controller
     * @param $action
     * @return bool
     */
    protected function checkController($controller, $action) {
        $controller = ucfirst($controller);
        $action = strtolower($action);
        $controllerClassName = 'App\\' . ucfirst(MODULE_NAME) . '\\Controller\\' . $controller;

        if (!method_exists($controllerClassName, $action)) {
            return false;
        }
        return [$controllerClassName, $action];
    }

    /**
     * 获取匹配到的路由规则
     * @param $pathInfo  当前url中pathinfo部分
     * @param $routes   待匹配的路由合集
     * @return bool|mixed   匹配失败 false  匹配成功 对应路由规则
     * @throws Exception    如果路由规则出现匹配错误，直接抛出异常
     */
    public function matchRoute($pathInfo, $routes) {
        if (empty($routes)) {
            return false;
        }
        $pathInfo = '/' . $pathInfo;
        foreach ($routes as $rule => $route) {
            $vars = [];
            $originalRule = $rule;

            $rule = '/' . trim($rule, '/');
            $completeMatch = isset($route['option']['complete_match']) && $route['option']['complete_match'] ? true : false;
            //路由规则不包含变量，则直接进行字符串匹配
            if (false === strpos($rule, '<')) {
                if (0 === strcasecmp($rule, $pathInfo) || (!$completeMatch && 0 === strncmp($rule . '/', $pathInfo . '/', strlen($rule . '/')))) {
                    $route['vars'] = $vars;
                    $route['rule'] = $originalRule;
                    return $route;
                }
                continue;
            }

            //路由规则包含参数，进行正则匹配
            if ($matchRule = preg_split('/[\/]?<\w+\??>/', $rule, 2)) {
                if ($matchRule[0] && 0 !== strncasecmp($rule, $pathInfo, strlen($matchRule[0]))) {
                    //路由标识和域名的前半部分不一致，则匹配失败
                    continue;
                }
            }

            if (preg_match_all('/[\/]?<?\w+\??>?/', $rule, $matches)) {
                //动态生成正则匹配表达式
                $regex = '';
                foreach ($matches[0] as $name) {
                    $name = substr($name, 1);
                    $slash = substr($name, 0, 1);
                    if ('<' != $slash) {
                        $regex .= '\/' . preg_quote($name, '/');
                    } else {
                        //判断是否可选参数
                        $optional = strpos($name, '?') ? '?' : '';
                        $name = str_replace(['?', '/', '<', '>'], '', $name);
                        $regex .= '(\/(?<' . $name . '>\w+))' . $optional;
                    }
                }
                $pattern = '/^' . $regex . ($completeMatch ? '$' : '') . '/u';
                try {
                    if (!preg_match($pattern, $pathInfo, $match)) {
                        continue;
                    }
                } catch (Exception $e) {
                    throw new Exception('路由生成的正则匹配存在问题:' . $pattern);
                }

                foreach ($match as $key => $val) {
                    if (is_string($key)) {
                        $vars[$key] = $val;
                    }
                }
                $route['vars'] = $vars;
                $route['rule'] = $originalRule;
                return $route;
            }

        }

        return false;
    }

    /**
     * 处理响应类
     * @param $request
     * @param $response
     * @return Response
     */
    public function prepareResponse($request, $response) {
        if ($response instanceof Responsable) {
            return $response;
        } elseif (!$response instanceof Response) {
            $response = new Response($response);
        }

        return $response->prepare($request);
    }
}